﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace Allegro.TaskExecution
{
    /// <summary>
    /// Task execution context for parallel execution. Supports context derivation
    /// </summary>
    public class ParallelTaskExecutionContext : BasicTaskExecutionContext, ITaskContextDerivation
    {
        private IExecutionContext _nestedContext;
        private IExecutionContext _rootContext;
        private Dictionary<object, IExecutionContext> _derivedContexts;

        /// <summary>
        /// 
        /// </summary>
        public ParallelTaskExecutionContext()
        {
            _derivedContexts = new Dictionary<object, IExecutionContext>();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="nestedContext"></param>
        protected ParallelTaskExecutionContext(IExecutionContext nestedContext) 
        {
            _derivedContexts = new Dictionary<object, IExecutionContext>();
            _nestedContext = nestedContext;
            if(nestedContext is ITaskContextDerivation)
            {
                _rootContext = ((ITaskContextDerivation) nestedContext).RootContext;
                if (_rootContext == null)
                    _rootContext = nestedContext;
            }
            else
            {
                _rootContext = nestedContext;
            }
        }
        /// <summary>
        /// Fork a context
        /// </summary>
        /// <returns></returns>
        public virtual IExecutionContext Fork()
        {
            if(_nestedContext == null)
            {
                string msg = "Nested context is null, unable to fork";
                Log.Error(msg, this, LogEventTypes.OtherEvent);
                throw new ApplicationException(msg);
            }
            //ParallelTaskExecutionContext forkedContext = new ParallelTaskExecutionContext(_nestedContext);
            //IExecutionContext forkedContext = New(this["this"], _nestedContext);
            IExecutionContext forkedContext = New(This, _nestedContext);
            var variables = ContextVariables;
            Monitor.Enter(variables);
            foreach (string varName in variables.Keys)
            {
                forkedContext[varName] = variables[varName];
            }
            Monitor.Exit(variables);
            return forkedContext;
        }
        /// <summary>
        /// Create new context
        /// </summary>
        /// <param name="self"></param>
        /// <returns></returns>
        public virtual IExecutionContext New(object self)
        {
            //ParallelTaskExecutionContext forkedContext = new ParallelTaskExecutionContext(this);
            //return forkedContext;
            return New(self, self, this);
        }
        /// <summary>
        /// Create new context
        /// </summary>
        /// <param name="index"></param>
        /// <param name="self"></param>
        /// <returns></returns>
        public virtual IExecutionContext New(object index, object self)
        {
            return New(index, self, this);
        }
        /// <summary>
        /// Create new context
        /// </summary>
        /// <param name="index"></param>
        /// <param name="self"></param>
        /// <param name="nestedContext"></param>
        /// <returns></returns>
        protected virtual IExecutionContext New(object index, object self, IExecutionContext nestedContext)
        {
            ParallelTaskExecutionContext newContext =  new ParallelTaskExecutionContext(nestedContext);
            //newContext["this"] = self;
            ConnectDerivedContext(newContext, self, index);
            return newContext;
        }
        /// <summary>
        /// Connect derived context
        /// </summary>
        /// <param name="newContext"></param>
        /// <param name="self"></param>
        /// <param name="index"></param>
        protected void ConnectDerivedContext(ParallelTaskExecutionContext newContext, object self, object index)
        {
            newContext.This = self;
            Monitor.Enter(_derivedContexts);
            _derivedContexts.Add(index, newContext);
            Monitor.Exit(_derivedContexts);
        }
        /// <summary>
        /// Outer context
        /// </summary>
        public override IExecutionContext OuterContext
        {
            get { return _nestedContext; }
        }

        /// <summary>
        /// Root context. Null if this is a root context
        /// </summary>
        public override IExecutionContext RootContext
        {
            get { return _rootContext; }
        }

        /// <summary>
        /// Derived contexts
        /// </summary>
        public IDictionary<object, IExecutionContext> DerivedTaskContexts
        {
            get { return _derivedContexts; }
        }

        /// <summary>
        /// Search for the context in the derived context tree below the current context. If no associated 
        /// context is found, return null.
        /// </summary>
        /// <param name="contextKey"></param>
        /// <returns></returns>
        public IExecutionContext FindTaskExecutionContext(object contextKey)
        {
            IExecutionContext context = null;
            Monitor.Enter(_derivedContexts);
            if (DerivedTaskContexts.ContainsKey(contextKey))
                context = DerivedTaskContexts[contextKey];
            else
            {
                foreach (IExecutionContext c in DerivedTaskContexts.Values)
                {
                    if(c is ITaskContextDerivation)
                    {
                        context = ((ITaskContextDerivation) c).FindTaskExecutionContext(contextKey);
                        if(context != null)
                            break;
                    }
                }
            }
            Monitor.Exit(_derivedContexts);
            return context;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="varName"></param>
        /// <returns></returns>
        public override bool HasVariable(string varName)
        {
            bool hasVariable = base.HasVariable(varName);
            if (!hasVariable && _nestedContext != null)
                hasVariable = _nestedContext.HasVariable(varName);
            return hasVariable;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="varName"></param>
        /// <returns></returns>
        public override object GetValue(string varName)
        {
            object value = null;
            if(base.HasVariable(varName))
                value = base.GetValue(varName);
            else if(_nestedContext != null)
            {
                value = _nestedContext[varName];
            }
            
            return value;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="varName"></param>
        /// <param name="value"></param>
        public override void SetValue(string varName, object value)
        {
            if(base.HasVariable(varName))
                base.SetValue(varName, value);
            else if(_nestedContext != null && _nestedContext.HasVariable(varName))
                _nestedContext[varName] = value;
            else
            {
                base.SetValue(varName, value);
            }
        }
    }
}
